/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 - 2008 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/

package com.genia.toolbox.basics.bean.impl;

import java.lang.reflect.Method;
import java.util.Map;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import com.genia.toolbox.basics.bean.Transformer;
import com.genia.toolbox.basics.exception.BundledException;

/**
 * {@link MethodInterceptor} to have safe hash map.
 * 
 * @param <K>
 *          the type of the keys of the map
 * @param <V>
 *          the type of the values of the map
 */
public class SafeHashMapMethodInterceptor<K, V>
    implements MethodInterceptor
{

  /**
   * the object that allows to create entity for missing elements.
   */
  private final Transformer<K, V> tranformer;

  /**
   * the backing map.
   */
  private final Map<K, V> map;

  /**
   * whether the kind of map need a lock.
   */
  private final boolean needLock;



  /**
   * constructor.
   * 
   * @param map
   *          the backing map
   * @param tranformer
   *          the object that allows to create entity for missing elements. It
   *          must be synchronized for the result to have any meaning
   * @param needLock
   *          whether the kind of map need a lock
   */
  public SafeHashMapMethodInterceptor(Map<K, V> map, Transformer<K, V> tranformer, boolean needLock)
  {
    this.tranformer = tranformer;
    this.map = map;
    this.needLock = needLock;
  }



  /**
   * All generated proxied methods call this method instead of the original
   * method. The original method may either be invoked by normal reflection
   * using the Method object, or by using the MethodProxy (faster).
   * 
   * @param obj
   *          "this", the enhanced object
   * @param method
   *          intercepted Method
   * @param args
   *          argument array; primitive types are wrapped
   * @param proxy
   *          used to invoke super (non-intercepted method); may be called as
   *          many times as needed
   * @throws Throwable
   *           any exception may be thrown; if so, super method will not be
   *           invoked
   * @return any value compatible with the signature of the proxied method.
   *         Method returning void will ignore this value.
   * @see net.sf.cglib.proxy.MethodInterceptor#intercept(java.lang.Object,
   *      java.lang.reflect.Method, java.lang.Object[],
   *      net.sf.cglib.proxy.MethodProxy)
   */
  public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
      throws Throwable
  {
    if ("get".equals(method.getName()) && args.length == 1) {
      if (needLock) {
        synchronized (map) {
          return getValue(args[0]);
        }
      }
      else {
        return getValue(args[0]);
      }
    }
    return proxy.invoke(map, args);
  }



  /**
   * return the value of the {@link Map} for the given key.
   * 
   * @param arg
   *          the key to retrieve the value for
   * @return the value of the {@link Map} for the given key
   * @throws BundledException
   *           if an error occured
   */
  private Object getValue(Object arg)
      throws BundledException
  {
    @SuppressWarnings("unchecked")
    K key = (K) arg;
    // Double-Checked Locking is usable because map is a synchronized map.
    V result = map.get(key);
    if (result == null) {
      synchronized (map) {
        result = map.get(key);
        if (result == null) {
          result = tranformer.transform(key);
          map.put(key, result);
        }
      }
    }
    return result;
  }
}
